#include "shared.h"
#include "main.h"






/****************************
*           UTILS           *
****************************/

static inline const struct r5u870_resolution* webcamGetFormatResolutionTable( struct driverInstance* this, __u32 format ){
	
	int i;
	for( i = 0; i < this->webcamFormatsCount; i++ ){
		if( this->webcamFormats[i].pixelformat == format ){
			return this->webcamFormats[i].resolutionTable;
		}
	}
	return NULL;
}

/****************************
*   videoDevice ioctl ops   *
****************************/

//These callbacks are protected by this->videoDevice.lock. Don't worry!

static int vidioc_querycap_callback( struct file* file, void* fh, struct v4l2_capability* cap ){
	
	struct driverInstance* this = video_drvdata( file );
	const char busName[] = "USB";
	
	strlcpy( cap->driver,   KBUILD_MODNAME,               sizeof(cap->driver) );
	strlcpy( cap->card ,    this->webcamModel->modelName, sizeof(cap->card) );
	strlcpy( cap->bus_info, busName,                      sizeof(cap->bus_info) );
	
	cap->version = KERNEL_VERSION( 2, 0, 0 );
	
	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS;
	cap->device_caps  = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
	
	return 0;
}



static int vidioc_enum_fmt_vid_cap_callback( struct file* file, void* fh, struct v4l2_fmtdesc* f ){
	
	struct driverInstance* this = video_drvdata( file );
	const struct r5u870_pixelformat* pixelformat;
	
	if( f->index >= this->webcamFormatsCount ) return -EINVAL;
	
	pixelformat = &this->webcamFormats[ f->index ];
	f->flags = pixelformat->flags;
	f->pixelformat = pixelformat->pixelformat;
	strlcpy( f->description, pixelformat->description, sizeof(f->description) );
	
	return 0;
}

static int vidioc_g_fmt_vid_cap_callback( struct file* file, void* fh, struct v4l2_format* f ){
	struct driverInstance* this = video_drvdata( file );
	f->fmt.pix = this->webcamCurrentPixelformat;
	return 0;
}

static int vidioc_s_fmt_vid_cap_callback( struct file* file, void* fh, struct v4l2_format* f ){
	
	struct driverInstance* this = video_drvdata( file );
	int res;
	
	if( atomic_read( &this->usbDeviceIsDisconnected ) ){
		printk( KERN_WARNING "[%s] VIDIOC_S_FMT: device disconnected\n", KBUILD_MODNAME );
		return -EIO;
	}
	
	if( !memcmp(&f->fmt.pix, &this->webcamCurrentPixelformat, sizeof(f->fmt.pix)) ){
		printk( KERN_NOTICE "[%s] VIDIOC_S_FMT: nothing to do\n", KBUILD_MODNAME );
		return 0;
	}
	
	if( atomic_read(&this->webcamIsCaptuing) ){
		printk( KERN_WARNING "[%s] VIDIOC_S_FMT: capture in progress\n", KBUILD_MODNAME );
		return -EBUSY;
	}
	
	res = webcamTryFormat( this, &f->fmt.pix, 1 );
	if( res )printk( KERN_ERR "[%s] VIDIOC_S_FMT: err: %i\n", KBUILD_MODNAME, res );
	return res;
}

static int vidioc_try_fmt_vid_cap_callback( struct file* file, void* fh, struct v4l2_format* f ){
	struct driverInstance* this = video_drvdata( file );
	if ( atomic_read( &this->usbDeviceIsDisconnected ) ) return -EIO;
	return webcamTryFormat( this, &f->fmt.pix, 0 );
}



static int vidioc_enum_input_callback( struct file* file, void* fh, struct v4l2_input* input ){
	
	const char camName[] = "Camera";
	
	if( input->index ) return -EINVAL;
	
	strlcpy( input->name, camName, sizeof(input->name) );
	input->type = V4L2_INPUT_TYPE_CAMERA;
	
	return 0;
}

static int vidioc_g_input_callback( struct file* file, void* fh, unsigned int* i ){
	*i = 0;
	return 0;
}

static int vidioc_s_input_callback( struct file* file, void* fh, unsigned int i ){
	return ( i ? -EINVAL : 0 );
}


/*
TODO: Come up with something with standards.
I don’t know what to specify as the standard, so I just write 0. 
Some programs, for example skype, do not work if these functions are not implemented.
*/
static int vidioc_querystd_callback( struct file* file, void* fh, v4l2_std_id* argp ){
	printk( KERN_NOTICE "[%s] FIXME: %s is not implemented!\n", KBUILD_MODNAME, __FUNCTION__ );
	*argp = 0;
	return 0;
}

static int vidioc_g_std_callback( struct file* file, void* fh, v4l2_std_id* argp ){
	printk( KERN_NOTICE "[%s] FIXME: %s is not implemented!\n", KBUILD_MODNAME, __FUNCTION__ );
	*argp = 0;
	return 0;
}

static int vidioc_s_std_callback( struct file* file, void* fh, v4l2_std_id argp ){
	printk( KERN_NOTICE "[%s] FIXME: %s is not implemented!\n", KBUILD_MODNAME, __FUNCTION__ );
	return 0;
}





static int vidioc_enum_framesizes_callback( struct file* file, void* fh, struct v4l2_frmsizeenum* fsize ){
	
	struct driverInstance* this = video_drvdata( file );
	const struct r5u870_resolution* resolutionTable;
	int i;
	
	resolutionTable = webcamGetFormatResolutionTable( this, fsize->pixel_format );
	if( !resolutionTable ) return -EINVAL;
	
	for( i = 0; resolutionTable[i].width; i++ ){
		if( i == fsize->index ){
			fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
			fsize->discrete.width  = resolutionTable[i].width;
			fsize->discrete.height = resolutionTable[i].height;
			return 0;
		}
	}
	return -EINVAL;
}

static int vidioc_enum_frameintervals_callback( struct file* file, void* fh, struct v4l2_frmivalenum* fival ){
	
	struct driverInstance* this = video_drvdata( file );
	const struct r5u870_resolution* resolutionTable;
	int i;
	
	if( fival->index ) return -EINVAL;
	
	resolutionTable = webcamGetFormatResolutionTable( this, fival->pixel_format );
	if( !resolutionTable ) return -EINVAL;
	
	for( i = 0; resolutionTable[i].width; i++ ){
		if( fival->width == resolutionTable[i].width && fival->height == resolutionTable[i].height ){
			fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
			fival->discrete.numerator = resolutionTable[i].width * resolutionTable[i].height * 2;
			fival->discrete.denominator = resolutionTable[i].requiredBandwidth;
			return 0;
		}
	}
	
	return -EINVAL;
}


/****************************
*      videobuf2 ops        *
****************************/

//These callbacks are protected by this->videobuf2Queue.lock.

static int queue_setup_callback( struct vb2_queue* videobufQueue, unsigned int* numBuffers, unsigned int* numPlanes, unsigned int sizes[], struct device* alloc_devs[] ){
	
	struct driverInstance* this = videobufQueue->drv_priv;
	
	if( *numBuffers < VIDEOBUF_QUEUE_MIN_BUF_NEED ) *numBuffers = VIDEOBUF_QUEUE_MIN_BUF_NEED;
	*numPlanes = 1;
	sizes[0] = this->webcamCurrentPixelformat.sizeimage;
	
	return 0;
}

//called every time the buffer is queued from userspace and from the VIDIOC_PREPARE_BUF ioctl
static int buf_prepare_callback( struct vb2_buffer* videoBuffer ){
	
	struct driverInstance* this = vb2_get_drv_priv( videoBuffer->vb2_queue );
	
	if ( vb2_plane_size( videoBuffer, 0 ) < this->webcamCurrentPixelformat.sizeimage ) {
		printk( KERN_ERR "[%s] buffer too small (%lu < %u)\n", KBUILD_MODNAME, vb2_plane_size( videoBuffer, 0 ), this->webcamCurrentPixelformat.sizeimage );
		return -EINVAL;
	}
	
	vb2_set_plane_payload( videoBuffer, 0, this->webcamCurrentPixelformat.sizeimage );
	return 0;
}

//passes buffer to the driver; driver should give the buffer back by calling vb2_buffer_done() function
static void buf_queue_callback( struct vb2_buffer* videoBuffer ){
	
	struct driverInstance* this = vb2_get_drv_priv( videoBuffer->vb2_queue );
	struct videobuffer_capsule* videobufCapsule = container_of( videoBuffer, struct videobuffer_capsule, videobuffer );
	unsigned long flags = 0;
	
	if( atomic_read( &this->buflistIsDisabled ) ){
		videoBuffer->timestamp = ktime_get_ns();
		vb2_buffer_done( videoBuffer, VB2_BUF_STATE_ERROR );
	}else{
		spin_lock_irqsave( &this->buflistSpinlock, flags );
		list_add_tail( &videobufCapsule->listItem, &this->buflist );
		spin_unlock_irqrestore( &this->buflistSpinlock, flags );
	}
}

static int start_streaming_callback( struct vb2_queue* videobufQueue, unsigned int count ){
	struct driverInstance* this = vb2_get_drv_priv( videobufQueue );
	return webcamCaptureStart( this );
}

static void stop_streaming_callback( struct vb2_queue* videobufQueue ){
	struct driverInstance* this = videobufQueue->drv_priv;
	webcamCaptureStop( this );
	atomic_set( &this->buflistIsDisabled, 0 );
}

/****************************
*           USB             *
****************************/

static int usbDeviceProbe_callback( struct usb_interface* usbInterface, const struct usb_device_id* usbDeviceId ){
	
	int err = 0;
	
	//void * devm_kzalloc(struct device * dev, size_t size, gfp_t gfp);
	struct driverInstance* this = devm_kzalloc( &usbInterface->dev, sizeof(struct driverInstance), GFP_KERNEL );
	if( !this ){
		printk( KERN_ERR "[%s] devm_kzalloc err\n", KBUILD_MODNAME );
		return -ENOMEM;
	}
	
	this->usbInterface    = usb_get_intf( usbInterface );//NEED CLEAN
	this->usbDevice       = usb_get_dev( interface_to_usbdev(usbInterface) );//NEED CLEAN
	this->usbDeviceDriver = to_usb_driver( usbInterface->dev.driver );
	this->dev = &usbInterface->dev;
	
	INIT_LIST_HEAD( &this->buflist );
	mutex_init( &this->lock );//Initialize the mutex to unlocked state.
	spin_lock_init( &this->buflistSpinlock );
	INIT_LIST_HEAD( &this->claimedInterfacesList );
	
	err = webcamInit( this, usbDeviceId );//NEED CLEAN
	if( err ){
		printk( KERN_ERR "[%s] WebcamInit failed: %i\n", KBUILD_MODNAME, err );
		goto l_put_interface_and_usb_device;
	}
	
	err = v4l2_device_register( this->dev, &this->v4l2Device );//NEED CLEAN
	if( err ){
		printk( KERN_ERR "[%s] v4l2_device_register result: %i\n", KBUILD_MODNAME, err );
		goto l_webcamRelease;
	}
	
	//videobuf2 queue init
	this->videobuf2Queue.type               = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	this->videobuf2Queue.io_modes           = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
	this->videobuf2Queue.drv_priv           = this;
	this->videobuf2Queue.buf_struct_size    = sizeof( struct videobuffer_capsule );
	this->videobuf2Queue.ops                = &videobuf2QueueOps;
	this->videobuf2Queue.mem_ops            = &vb2_vmalloc_memops;//videobuf2MemOps
	this->videobuf2Queue.timestamp_flags    = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
	this->videobuf2Queue.min_buffers_needed = VIDEOBUF_QUEUE_MIN_BUF_NEED;
	this->videobuf2Queue.lock               = &this->lock;
	this->videobuf2Queue.gfp_flags          = GFP_KERNEL;//GFP_DMA32
	err = vb2_queue_init( &this->videobuf2Queue );
	if( err ){
		printk( KERN_ERR "[%s] vb2_queue_init err: %i\n", KBUILD_MODNAME, err );
		goto l_v4l2_device_unregister;
	}
	
	//videoDevice init
	strlcpy( this->videoDevice.name, KBUILD_MODNAME, sizeof(this->videoDevice.name) );
	this->videoDevice.release     = video_device_release_empty;
	this->videoDevice.fops        = &v4l2FileOperations;
	this->videoDevice.ioctl_ops   = &videoDeviceIoctlOps;
	this->videoDevice.lock        = &this->lock;//This lock will be automatically applied in ioctl_ops.
	this->videoDevice.queue       = &this->videobuf2Queue;
	this->videoDevice.v4l2_dev    = &this->v4l2Device;
	this->videoDevice.tvnorms     = V4L2_STD_ALL;
	this->videoDevice.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
	video_set_drvdata( &this->videoDevice, this );
	err = video_register_device( &this->videoDevice, VFL_TYPE_VIDEO, -1 );//NEED CLEAN
	if( err ){
		printk( KERN_ERR "[%s] video_register_device err: %i\n", KBUILD_MODNAME, err );
		goto l_v4l2_device_unregister;
	}
	
	this->urbstreamWorkqueue = create_singlethread_workqueue( KBUILD_MODNAME );//NEED CLEAN
	
	//static inline void usb_set_intfdata(struct usb_interface *intf, void *data);
	usb_set_intfdata( usbInterface, this );
	return err;
	
	
l_v4l2_device_unregister:
	v4l2_device_unregister( &this->v4l2Device );
	
l_webcamRelease:
	webcamRelease( this );
	
l_put_interface_and_usb_device:
	usb_put_dev( this->usbDevice );
	usb_put_intf( this->usbInterface );
	
	return err;
}

//also called befoure module unloading
static void usbDeviceDisconnect_callback( struct usb_interface* usbInterface ){
	
	//static inline void *usb_get_intfdata(struct usb_interface *intf);
	struct driverInstance* this = usb_get_intfdata( usbInterface );
	
	atomic_set( &this->usbDeviceIsDisconnected, 1 );
	webcamRelease( this );
	
	flush_workqueue( this->urbstreamWorkqueue );
	destroy_workqueue( this->urbstreamWorkqueue );
	
	video_unregister_device( &this->videoDevice );
	v4l2_device_unregister( &this->v4l2Device );
	
	usb_set_intfdata( usbInterface, NULL );
	
	usb_put_dev( this->usbDevice );
	usb_put_intf( this->usbInterface );
}

/****************************
*    module entry point     *
****************************/

static int onLoad( void ){
	printk( KERN_NOTICE "%s is here.\n", KBUILD_MODNAME );
	return usb_register( &usbDriver_s );
}

static void onUnload( void ){
	usb_deregister( &usbDriver_s );
	printk( KERN_NOTICE "%s has been unloaded.\n", KBUILD_MODNAME );
}

module_init( onLoad );
module_exit( onUnload );
MODULE_LICENSE( "GPL" );


